#include "options.h"

#include "WiFi.h"
#include "time.h"
#include "display.h"
#include "player.h"
#include "network.h"
#include <U8g2_for_Adafruit_GFX.h>

//#include "../fonts/u8g2_mfyuanhei_16_gb2312.h"
#include "../fonts/u8g2_mfmm_12_gb2312.h"
Display display;
#ifdef USE_NEXTION
Nextion nextion;
#endif

#ifndef DUMMYDISPLAY
//============================================================================================================================
DspCore dsp;
U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;

Page *pages[] = {new Page(), new Page(), new Page()};

#ifndef DSQ_SEND_DELAY
#define DSQ_SEND_DELAY portMAX_DELAY
#endif

#ifndef CORE_STACK_SIZE
#define CORE_STACK_SIZE 1024 * 3
#endif
#ifndef DSP_TASK_DELAY
#define DSP_TASK_DELAY 2
#endif
#if !((DSP_MODEL == DSP_ST7735 && DTYPE == INITR_BLACKTAB) || DSP_MODEL == DSP_ST7789 || DSP_MODEL == DSP_ST7796 || DSP_MODEL == DSP_ILI9488 || DSP_MODEL == DSP_ILI9486 || DSP_MODEL == DSP_ILI9341 || DSP_MODEL == DSP_ILI9225)
#undef BITRATE_FULL
#define BITRATE_FULL false
#endif
TaskHandle_t DspTask;
QueueHandle_t displayQueue;

void returnPlayer()
{
  display.putRequest(NEWMODE, PLAYER);
}

void Display::_createDspTask()
{
  xTaskCreatePinnedToCore(loopDspTask, "DspTask", CORE_STACK_SIZE, NULL, 4, &DspTask, !xPortGetCoreID());
}

void loopDspTask(void *pvParameters)
{
  while (true)
  {
    if (displayQueue == NULL)
      break;
    display.loop();
    vTaskDelay(DSP_TASK_DELAY);
  }
  vTaskDelete(NULL);
  DspTask = NULL;
}

void Display::init()
{
  Serial.print("##[BOOT]#\tdisplay.init\t");
#ifdef USE_NEXTION
  nextion.begin();
#endif
#if LIGHT_SENSOR != 255
  analogSetAttenuation(ADC_0db);
#endif
  _bootStep = 0;
  dsp.initDisplay();
  displayQueue = NULL;
  displayQueue = xQueueCreate(5, sizeof(requestParams_t));
  while (displayQueue == NULL)
  {
    ;
  }
  _createDspTask();
  while (!_bootStep == 0)
  {
    delay(10);
  }
  //_pager.begin();
  //_bootScreen();
  Serial.println("done");
  u8g2_for_adafruit_gfx.begin(dsp);
  //u8g2_for_adafruit_gfx.setFont(u8g2_mfyuanhei_16_gb2312);
  u8g2_for_adafruit_gfx.setFont(u8g2_mfmm_12_gb2312);
}

void Display::_bootScreen()
{
  _boot = new Page();
  _boot->addWidget(new ProgressWidget(bootWdtConf, bootPrgConf, BOOT_PRG_COLOR, 0));
  _bootstring = (TextWidget *)&_boot->addWidget(new TextWidget(bootstrConf, 50, true, BOOT_TXT_COLOR, 0));
  _pager.addPage(_boot);
  _pager.setPage(_boot, true);
  dsp.drawLogo(bootLogoTop);
  _bootStep = 1;
}

void Display::_buildPager()
{
  _meta.init("*", metaConf, config.theme.meta, config.theme.metabg);
  _title1.init("*", title1Conf, config.theme.title1, config.theme.background);
  _clock.init(clockConf, 0, 0);
#if DSP_MODEL == DSP_NOKIA5110
  _plcurrent.init("*", playlistConf, 0, 1);
#else
  _plcurrent.init("*", playlistConf, config.theme.plcurrent, config.theme.plcurrentbg);
#endif
#if !defined(DSP_LCD)
  _plcurrent.moveTo({TFT_FRAMEWDT, (uint16_t)(dsp.plYStart + dsp.plCurrentPos * dsp.plItemHeight), (int16_t)playlistConf.width});
#endif
#ifndef HIDE_TITLE2
  _title2 = new ScrollWidget("*", title2Conf, config.theme.title2, config.theme.background);
#endif
#if !defined(DSP_LCD) && DSP_MODEL != DSP_NOKIA5110
  _plbackground = new FillWidget(playlBGConf, config.theme.plcurrentfill);
#if DSP_INVERT_TITLE || defined(DSP_OLED)
  _metabackground = new FillWidget(metaBGConf, config.theme.metafill);
#else
  _metabackground = new FillWidget(metaBGConfInv, config.theme.metafill);
#endif
#endif
#if DSP_MODEL == DSP_NOKIA5110
  _plbackground = new FillWidget(playlBGConf, 1);
  //_metabackground = new FillWidget(metaBGConf, 1);
#endif
#ifndef HIDE_VU
  _vuwidget = new VuWidget(vuConf, bandsConf, config.theme.vumax, config.theme.vumin, config.theme.background);
#endif
#ifndef HIDE_VOLBAR
  _volbar = new SliderWidget(volbarConf, config.theme.volbarin, config.theme.background, 254, config.theme.volbarout);
#endif
#ifndef HIDE_HEAPBAR
  _heapbar = new SliderWidget(heapbarConf, config.theme.buffer, config.theme.background, psramInit() ? 300000 : 1600 * AUDIOBUFFER_MULTIPLIER2);
#endif
#ifndef HIDE_VOL
  _voltxt = new TextWidget(voltxtConf, 10, false, config.theme.vol, config.theme.background);
#endif
#ifndef HIDE_IP
  _volip = new TextWidget(iptxtConf, 30, false, config.theme.ip, config.theme.background);
#endif
#ifndef HIDE_RSSI
  _rssi = new TextWidget(rssiConf, 20, false, config.theme.rssi, config.theme.background);
#endif
  _nums.init(numConf, 10, false, config.theme.digit, config.theme.background);
#ifndef HIDE_WEATHER
  _weather = new ScrollWidget("\007", weatherConf, config.theme.weather, config.theme.background);
#endif

  if (_volbar)
    _footer.addWidget(_volbar);
  if (_voltxt)
    _footer.addWidget(_voltxt);
  if (_volip)
    _footer.addWidget(_volip);
  if (_rssi)
    _footer.addWidget(_rssi);
  if (_heapbar)
    _footer.addWidget(_heapbar);

  if (_metabackground)
    pages[PG_PLAYER]->addWidget(_metabackground);
  pages[PG_PLAYER]->addWidget(&_meta);
  pages[PG_PLAYER]->addWidget(&_title1);
  if (_title2)
 //   pages[PG_PLAYER]->addWidget(_title2);
  if (_weather)
    pages[PG_PLAYER]->addWidget(_weather);
#if BITRATE_FULL
  _fullbitrate = new BitrateWidget(fullbitrateConf, config.theme.bitrate, config.theme.background);
  pages[PG_PLAYER]->addWidget(_fullbitrate);
#else
  _bitrate = new TextWidget(bitrateConf, 30, false, config.theme.bitrate, config.theme.background);
  pages[PG_PLAYER]->addWidget(_bitrate);
#endif
  if (_vuwidget)
    pages[PG_PLAYER]->addWidget(_vuwidget);
  pages[PG_PLAYER]->addWidget(&_clock);
  pages[PG_PLAYER]->addPage(&_footer);

  if (_metabackground)
    pages[PG_DIALOG]->addWidget(_metabackground);
  pages[PG_DIALOG]->addWidget(&_meta);
  pages[PG_DIALOG]->addWidget(&_nums);

#if !defined(DSP_LCD) && DSP_MODEL != DSP_NOKIA5110
  pages[PG_DIALOG]->addPage(&_footer);
#endif
#if !defined(DSP_LCD)
  if (_plbackground)
  {
    pages[PG_PLAYLIST]->addWidget(_plbackground);
    _plbackground->setHeight(dsp.plItemHeight);
    _plbackground->moveTo({0, (uint16_t)(dsp.plYStart + dsp.plCurrentPos * dsp.plItemHeight - playlistConf.widget.textsize * 2), (int16_t)playlBGConf.width});
  }
#endif
  pages[PG_PLAYLIST]->addWidget(&_plcurrent);

  for (const auto &p : pages)
    _pager.addPage(p);
}

void Display::_apScreen()
{
  if (_boot)
    _pager.removePage(_boot);
#ifndef DSP_LCD
  _boot = new Page();
#if DSP_MODEL != DSP_NOKIA5110
#if DSP_INVERT_TITLE || defined(DSP_OLED)
  _boot->addWidget(new FillWidget(metaBGConf, config.theme.metafill));
#else
  _boot->addWidget(new FillWidget(metaBGConfInv, config.theme.metafill));
#endif
#endif
  ScrollWidget *bootTitle = (ScrollWidget *)&_boot->addWidget(new ScrollWidget("*", apTitleConf, config.theme.meta, config.theme.metabg));
  bootTitle->setText("ёRadio AP Mode");
  TextWidget *apname = (TextWidget *)&_boot->addWidget(new TextWidget(apNameConf, 30, false, config.theme.title1, config.theme.background));
  apname->setText(apNameTxt);
  TextWidget *apname2 = (TextWidget *)&_boot->addWidget(new TextWidget(apName2Conf, 30, false, config.theme.clock, config.theme.background));
  apname2->setText(apSsid);
  TextWidget *appass = (TextWidget *)&_boot->addWidget(new TextWidget(apPassConf, 30, false, config.theme.title1, config.theme.background));
  appass->setText(apPassTxt);
  TextWidget *appass2 = (TextWidget *)&_boot->addWidget(new TextWidget(apPass2Conf, 30, false, config.theme.clock, config.theme.background));
  appass2->setText(apPassword);
  ScrollWidget *bootSett = (ScrollWidget *)&_boot->addWidget(new ScrollWidget("*", apSettConf, config.theme.title2, config.theme.background));
  bootSett->setText(WiFi.softAPIP().toString().c_str(), apSettFmt);
  _pager.addPage(_boot);
  _pager.setPage(_boot);
#else
  dsp.apScreen();
#endif
}

void Display::_start()
{
  if (_boot)
    _pager.removePage(_boot);
#ifdef USE_NEXTION
  nextion.wake();
#endif
  if (network.status != CONNECTED && network.status != SDREADY)
  {
    _apScreen();
#ifdef USE_NEXTION
    nextion.apScreen();
#endif
    _bootStep = 2;
    return;
  }
#ifdef USE_NEXTION
  // nextion.putcmd("page player");
  nextion.start();
#endif
  _buildPager();
  _mode = PLAYER;
  config.setTitle(const_PlReady);

  if (_heapbar)
    _heapbar->lock(!config.store.audioinfo);

  if (_weather)
    _weather->lock(!config.store.showweather);
  if (_weather && config.store.showweather)
    _weather->setText(const_getWeather);

  if (_vuwidget)
    _vuwidget->lock();
  if (_rssi)
    _setRSSI(WiFi.RSSI());
#ifndef HIDE_IP
  if (_volip)
    _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt);
#endif
  _pager.setPage(pages[PG_PLAYER]);
  _volume();
  _station();
  _time(false);
  _bootStep = 2;
}

void Display::_showDialog(const char *title)
{
  dsp.setScrollId(NULL);
  _pager.setPage(pages[PG_DIALOG]);
#ifdef META_MOVE
  _meta.moveTo(metaMove);
#endif
  _meta.setAlign(WA_CENTER);
  _meta.setText(title);
}

void Display::_setReturnTicker(uint8_t time_s)
{
  _returnTicker.detach();
  _returnTicker.once(time_s, returnPlayer);
}

void Display::_swichMode(displayMode_e newmode)
{
#ifdef USE_NEXTION
  // nextion.swichMode(newmode);
  nextion.putRequest({NEWMODE, newmode});
#endif
  if (newmode == _mode || (network.status != CONNECTED && network.status != SDREADY))
    return;
  _mode = newmode;
  dsp.setScrollId(NULL);
  if (newmode == PLAYER)
  {
#ifdef DSP_LCD
    dsp.clearDsp();
#endif
    numOfNextStation = 0;
    _returnTicker.detach();
#ifdef META_MOVE
    _meta.moveBack();
#endif
    _meta.setAlign(metaConf.widget.align);
    _meta.setText(config.station.name);
    _nums.setText("");
    _pager.setPage(pages[PG_PLAYER]);
  }
  if (newmode == VOL)
  {
#ifndef HIDE_IP
    _showDialog(const_DlgVolume);
#else
    _showDialog(WiFi.localIP().toString().c_str());
#endif
    _nums.setText(config.store.volume, numtxtFmt);
  }
  if (newmode == LOST)
    _showDialog(const_DlgLost);
  if (newmode == UPDATING)
    _showDialog(const_DlgUpdate);
  if (newmode == SLEEPING)
    _showDialog("SLEEPING");
  if (newmode == SDCHANGE)
    _showDialog(const_waitForSD);
  if (newmode == INFO || newmode == SETTINGS || newmode == TIMEZONE || newmode == WIFI)
    _showDialog(const_DlgNextion);
  if (newmode == NUMBERS)
    _showDialog("");
  if (newmode == STATIONS)
  {
    _pager.setPage(pages[PG_PLAYLIST]);
    _plcurrent.setText("");
    currentPlItem = config.store.lastStation;
    _drawPlaylist();
  }
}

void Display::resetQueue()
{
  if (displayQueue != NULL)
    xQueueReset(displayQueue);
}

void Display::_drawPlaylist()
{
  dsp.drawPlaylist(currentPlItem);
  _setReturnTicker(30);
}

void Display::_drawNextStationNum(uint16_t num)
{
  _setReturnTicker(30);
  _meta.setText(config.stationByNum(num));
  _nums.setText(num, "%d");
}

void Display::printPLitem(uint8_t pos, const char *item)
{
  dsp.printPLitemCHS(pos, item, _plcurrent);
}

void Display::putRequest(displayRequestType_e type, int payload)
{
  if (displayQueue == NULL)
    return;
  requestParams_t request;
  request.type = type;
  request.payload = payload;
  xQueueSend(displayQueue, &request, DSQ_SEND_DELAY);
#ifdef USE_NEXTION
  nextion.putRequest(request);
#endif
}

void Display::_layoutChange(bool played)
{
  if (config.store.vumeter)
  {
    if (played)
    {
      if (_vuwidget)
        _vuwidget->unlock();
      _clock.moveTo(clockMove);
      if (_weather)
        _weather->moveTo(weatherMoveVU);
    }
    else
    {
      if (_vuwidget)
        if (!_vuwidget->locked())
          _vuwidget->lock();
      _clock.moveBack();
      if (_weather)
        _weather->moveBack();
    }
  }
  else
  {
    if (played)
    {
      if (_weather)
        _weather->moveTo(weatherMove);
      _clock.moveBack();
    }
    else
    {
      if (_weather)
        _weather->moveBack();
      _clock.moveBack();
    }
  }
}
#ifndef DSP_QUEUE_TICKS
#define DSP_QUEUE_TICKS 0
#endif
void Display::loop()
{
  if (_bootStep == 0)
  {
    _pager.begin();
    _bootScreen();
    return;
  }
  if (displayQueue == NULL)
    return;
  _pager.loop();
#ifdef USE_NEXTION
  nextion.loop();
#endif
  requestParams_t request;
  if (xQueueReceive(displayQueue, &request, DSP_QUEUE_TICKS))
  {
    switch (request.type)
    {
    case NEWMODE:
      _swichMode((displayMode_e)request.payload);
      break;
    case CLOCK:
      if (_mode == PLAYER)
        _time();
      /*#ifdef USE_NEXTION
        if(_mode==TIMEZONE) nextion.localTime(network.timeinfo);
        if(_mode==INFO)     nextion.rssi();
      #endif*/
      break;
    case NEWTITLE:
      _title();
      break;
    case NEWSTATION:
      _station();
      break;
    case NEXTSTATION:
      _drawNextStationNum(request.payload);
      break;
    case DRAWPLAYLIST:
      _drawPlaylist();
      break;
    case DRAWVOL:
      _volume();
      break;
    case DBITRATE:
    {
      char buf[20];
      snprintf(buf, 20, bitrateFmt, config.station.bitrate);
      if (_bitrate)
      {
        _bitrate->setText(config.station.bitrate == 0 ? "" : buf);
      }
      if (_fullbitrate)
      {
        _fullbitrate->setBitrate(config.station.bitrate);
        _fullbitrate->setFormat(config.configFmt);
      }
    }
    break;
    case AUDIOINFO:
      if (_heapbar)
      {
        _heapbar->lock(!config.store.audioinfo);
        _heapbar->setValue(player.inBufferFilled());
      }
      break;
    case SHOWVUMETER:
    {
      if (_vuwidget)
      {
        _vuwidget->lock(!config.store.vumeter);
        _layoutChange(player.isRunning());
      }
      break;
    }
    case SHOWWEATHER:
    {
      if (_weather)
        _weather->lock(!config.store.showweather);
      if (!config.store.showweather)
      {
#ifndef HIDE_IP
        if (_volip)
          _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt);
#endif
      }
      else
      {
        if (_weather)
          _weather->setText(const_getWeather);
      }
      break;
    }
    case NEWWEATHER:
    {
      if (_weather && network.weatherBuf)
        _weather->setText(network.weatherBuf);
      break;
    }
    case BOOTSTRING:
    {
      if (_bootstring)
        _bootstring->setText(config.ssids[request.payload].ssid, bootstrFmt);
      /*#ifdef USE_NEXTION
        char buf[50];
        snprintf(buf, 50, bootstrFmt, config.ssids[request.payload].ssid);
        nextion.bootString(buf);
      #endif*/
      break;
    }
    case WAITFORSD:
    {
      if (_bootstring)
        _bootstring->setText(const_waitForSD);
      break;
    }
    case SDFILEINDEX:
    {
      if (_mode == SDCHANGE)
        _nums.setText(request.payload, "%d");
      break;
    }
    case DSPRSSI:
      if (_rssi)
      {
        _setRSSI(request.payload);
      }
      if (_heapbar && config.store.audioinfo)
        _heapbar->setValue(player.isRunning() ? player.inBufferFilled() : 0);
      break;
    case PSTART:
      _layoutChange(true);
      break;
    case PSTOP:
      _layoutChange(false);
      break;
    case DSP_START:
      _start();
      break;
    case NEWIP:
    {
#ifndef HIDE_IP
      if (_volip)
        _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt);
#endif
      break;
    }
    default:
      break;
    }
  }
  dsp.loop();
}

void Display::_setRSSI(int rssi)
{
  if (!_rssi)
    return;
#if RSSI_DIGIT
  _rssi->setText(rssi, rssiFmt);
  return;
#endif
  char rssiG[3];
  int rssi_steps[] = {RSSI_STEPS};
  if (rssi >= rssi_steps[0])
    strlcpy(rssiG, "\004\006", 3);
  if (rssi >= rssi_steps[1] && rssi < rssi_steps[0])
    strlcpy(rssiG, "\004\005", 3);
  if (rssi >= rssi_steps[2] && rssi < rssi_steps[1])
    strlcpy(rssiG, "\004\002", 3);
  if (rssi >= rssi_steps[3] && rssi < rssi_steps[2])
    strlcpy(rssiG, "\003\002", 3);
  if (rssi < rssi_steps[3] || rssi >= 0)
    strlcpy(rssiG, "\001\002", 3);
  _rssi->setText(rssiG);
}

void Display::_station()
{
  _meta.setAlign(metaConf.widget.align);
  _meta.setText(config.station.name);
  /*#ifdef USE_NEXTION
    nextion.newNameset(config.station.name);
    nextion.bitrate(config.station.bitrate);
    nextion.bitratePic(ICON_NA);
  #endif*/
}

char *split(char *str, const char *delim)
{
  char *dmp = strstr(str, delim);
  if (dmp == NULL)
    return NULL;
  *dmp = '\0';
  return dmp + strlen(delim);
}

void Display::_title()
{
  if (strlen(config.station.title) > 0)
  {
    char tmpbuf[strlen(config.station.title) + 1];
    strlcpy(tmpbuf, config.station.title, strlen(config.station.title) + 1);
    char *stitle = split(tmpbuf, " - ");
    if (stitle && _title2)
    {
      _title1.setText(tmpbuf);
      _title2->setText(stitle);
    }
    else
    {
      _title1.setText(config.station.title);
      if (_title2)
        _title2->setText("");
    }
    /*#ifdef USE_NEXTION
      nextion.newTitle(config.station.title);
    #endif*/
  }
  else
  {
    _title1.setText("");
    if (_title2)
      _title2->setText("");
  }
  if (player_on_track_change)
    player_on_track_change();
}

void Display::_time(bool redraw)
{

#if LIGHT_SENSOR != 255
  if (config.store.dspon)
  {
    config.store.brightness = AUTOBACKLIGHT(analogRead(LIGHT_SENSOR));
    config.setBrightness();
  }
#endif
  _clock.draw();
  /*#ifdef USE_NEXTION
    nextion.printClock(network.timeinfo);
  #endif*/
}

void Display::_volume()
{
  if (_volbar)
    _volbar->setValue(config.store.volume);
#ifndef HIDE_VOL
  if (_voltxt)
    _voltxt->setText(config.store.volume, voltxtFmt);
#endif
  if (_mode == VOL)
  {
    _setReturnTicker(3);
    _nums.setText(config.store.volume, numtxtFmt);
  }
  /*#ifdef USE_NEXTION
    nextion.setVol(config.store.volume, _mode == VOL);
  #endif*/
}

void Display::flip() { dsp.flip(); }

void Display::invert() { dsp.invert(); }

void Display::setContrast()
{
#if DSP_MODEL == DSP_NOKIA5110
  dsp.setContrast(config.store.contrast);
#endif
}

bool Display::deepsleep()
{
#if defined(LCD_I2C) || defined(DSP_OLED) || BRIGHTNESS_PIN != 255
  dsp.sleep();
  return true;
#endif
  return false;
}

void Display::wakeup()
{
#if defined(LCD_I2C) || defined(DSP_OLED) || BRIGHTNESS_PIN != 255
  dsp.wake();
#endif
}
//============================================================================================================================
#else // !DUMMYDISPLAY
//============================================================================================================================
void Display::init()
{
#ifdef USE_NEXTION
  nextion.begin(true);
#endif
}
void Display::_start()
{
#ifdef USE_NEXTION
  // nextion.putcmd("page player");
  nextion.start();
#endif
  config.setTitle(const_PlReady);
}
void Display::putRequest(displayRequestType_e type, int payload)
{
  if (type == DSP_START)
    _start();
#ifdef USE_NEXTION
  requestParams_t request;
  request.type = type;
  request.payload = payload;
  nextion.putRequest(request);
#else
  if (type == NEWMODE)
    mode((displayMode_e)payload);
#endif
}
//============================================================================================================================
#endif // DUMMYDISPLAY
